The DOM builder API provides a clean, declarative way to construct HTML elements from Space Lua. It is typically used to build API/widget|Widgets for rendering dynamic UI in your pages.

The full API reference is at API/dom.

Basic usage

Every HTML tag is available as a function on the dom table. Call it with a table of attributes and children:

dom.div {
  class = "my-container",
  dom.h2 { "Hello!" },
  dom.p { "This is a paragraph." }
}

This creates a <div> with a class attribute, containing an <h2> and a <p>.

Attributes and content

String keys in the table become HTML attributes. Numeric entries become children:

dom.a {
  href = "https://example.com",
  "Click here"
}

Markdown support: String children are automatically processed as markdown. So "**bold**" renders as bold text.

Nested elements: Other dom.* calls can be nested as children to build up a tree.

Event handlers

Attributes starting with on are registered as event listeners:

dom.button {
  onclick = function()
    editor.flashNotification("Clicked!")
  end,
  "Click me"
}

This is equivalent to calling addEventListener("click", fn) on the button element.

Rendering as a widget

DOM elements need to be wrapped in a widget to display on a page. Use widget.html for inline or widget.htmlBlock for block-level:

-- Inline widget (rendered within text flow)
widget.html(dom.span { class = "badge", "New" })

-- Block widget (gets its own block)
widget.htmlBlock(dom.table {
  dom.tr {
    dom.td { "Name" },
    dom.td { "Value" }
  }
})

Building tables dynamically

A common pattern is building HTML tables from query results:

local rows = {}
for page in query[ from index.tag "page" limit 5 ]( from index.tag "page" limit 5 ) do
  table.insert(rows, dom.tr {
    dom.td { "[" .. page.name .. "](" .. page.name .. ")" },
    dom.td { os.date("%Y-%m-%d", page.lastModified) }
  })
end

return widget.htmlBlock(dom.table {
  dom.thead {
    dom.tr {
      dom.td { "Page" },
      dom.td { "Modified" }
    }
  },
  dom.tbody(rows)
})

Embedding widgets inside DOM

Widget objects (like buttons from widgets.button) can be nested inside DOM elements:

dom.div {
  "Status: ",
  widgets.button("Refresh", function()
    editor.invokeCommand("System: Reload")
  end)
}

How it works

Under the hood, dom uses a Lua metatable so that any property access (e.g. dom.span) returns a constructor function. That function calls js.window.document.createElement(tag) and processes the spec table to set attributes, add event listeners, and append children.

See also: API/dom, API/widget, Space Lua/Widget